package com.mxx.common.data.jpa;

import org.springframework.data.jpa.domain.Specification;
import org.springframework.lang.Nullable;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.io.Serializable;

class UpdateSpecificationComposition {

    interface Combiner extends Serializable {
        Predicate combine(CriteriaBuilder builder, @Nullable Predicate lhs, @Nullable Predicate rhs);
    }

    @Nullable
    static <T> UpdateSpecification<T> composed(@Nullable UpdateSpecification<T> lhs, @Nullable Specification<T> rhs,
                                               UpdateSpecificationComposition.Combiner combiner) {

        return (root, criteriaBuilder, criteriaQuery, criteriaUpdate) -> {
            UpdateSpecification.Result rhsResult;
            if (rhs instanceof UpdateSpecification) {
                rhsResult = ((UpdateSpecification) rhs).buildUpdate(root, criteriaBuilder, criteriaQuery, criteriaUpdate);
            } else {
                rhsResult = new UpdateSpecification.Result(true, 0, rhs.toPredicate(root, criteriaQuery, criteriaBuilder));
            }
            UpdateSpecification.Result lhsResult = lhs.buildUpdate(root, criteriaBuilder, criteriaQuery, criteriaUpdate);
            Predicate lhsPredicate = lhsResult.getPredicate();
            Predicate rhsPredicate = rhsResult.getPredicate();

            Predicate predicate = lhsPredicate == null ? rhsPredicate : rhsPredicate == null ? lhsPredicate : combiner.combine(criteriaBuilder, lhsPredicate, rhsPredicate);

            return new UpdateSpecification.Result(lhsResult.isAllowNullCondition() || rhsResult.isAllowNullCondition(), lhsResult.getUpdateFieldNumber() + rhsResult.getUpdateFieldNumber(), predicate);
        };
    }

    private static <T> Predicate toPredicate(Specification<T> specification, Root<T> root, CriteriaQuery<?> query,
                                             CriteriaBuilder builder) {
        return specification == null ? null : specification.toPredicate(root, query, builder);
    }
}
